package com.spukmk3me.video;

import java.io.IOException;
import java.io.InputStream;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import com.spukmk3me.Framework;
import com.spukmk3me.debug.Logger;
import com.spukmk3me.io.FileHandle;
import com.spukmk3me.io.FileSystem;
import com.spukmk3me.math.Rectangle;
import com.spukmk3me.resource.Resource;
import com.spukmk3me.resource.ResourceManager;
import com.spukmk3me.util.StringUtil;

public class Font extends Resource
{
    public int getStringWidth( char[] s, int offset, int length )
    {
        if ( s == null )
            return 0;

        int border = offset + length;

        if ( border > s.length )
            border = s.length;

        if ( border <= offset )
            return 0;

        int     lineWidth, maxWidth;
        char    ch;
        // Small space added after each non-space character.
        boolean additionalSpace = false;

        lineWidth = maxWidth = 0;

        while ( offset != border )
        {
            ch = s[ offset ];

            if ( ch == '\n' )
            {
                lineWidth -= m_charSpace;

                if ( lineWidth > maxWidth )
                    maxWidth = lineWidth;

                lineWidth       = 0;
                additionalSpace = false;
            }
            else if ( ch != ' ' )
            {
                lineWidth += getCharacterDimension( ch ).w + m_charSpace;
                additionalSpace = true;
            }
            else
            {
                lineWidth += m_spaceDimension.w;
                
                if ( additionalSpace )
                    lineWidth -= m_charSpace;
                
                additionalSpace = false;
            }

            ++offset;
        }

        if ( additionalSpace )
            lineWidth -= m_charSpace;

        if ( lineWidth > maxWidth )
            maxWidth = lineWidth;

        return maxWidth;
    }
    
    /**
     *  Copied from the char[] version.
     */
    public int getStringWidth( String s, int offset, int length )
    {
        if ( s == null )
            return 0;

        int border = offset + length;

        if ( border > s.length() )
            border = s.length();

        if ( border <= offset )
            return 0;

        int     lineWidth, maxWidth;
        char    ch;
        // Small space added after each non-space character.
        boolean additionalSpace = false;

        lineWidth = maxWidth = 0;

        while ( offset != border )
        {
            ch = s.charAt( offset );

            if ( ch == '\n' )
            {
                lineWidth -= m_charSpace;

                if ( lineWidth > maxWidth )
                    maxWidth = lineWidth;

                lineWidth       = 0;
                additionalSpace = false;
            }
            else if ( ch != ' ' )
            {
                lineWidth += getCharacterDimension( ch ).w + m_charSpace;
                additionalSpace = true;
            }
            else
            {
                lineWidth += m_spaceDimension.w;
                
                if ( additionalSpace )
                    lineWidth -= m_charSpace;
                
                additionalSpace = false;
            }

            ++offset;
        }

        if ( additionalSpace )
            lineWidth -= m_charSpace;

        if ( lineWidth > maxWidth )
            maxWidth = lineWidth;

        return maxWidth;
    }
    
    public final boolean isSupported( char c )
    {
        if ( c == ' ' )
            return true;

        return m_charMap.indexOf( c ) != -1;
    }

    public final int getLineHeight()
    {
        return m_lineHeight;
    }
    
    public final int getSpaceBetweenChar()
    {
        return m_charSpace;
    }
    
    public final Rectangle getCharacterDimension( char c )
    {
        if ( c == ' ' )
            return m_spaceDimension;
        
        int index = m_charMap.indexOf( c );
        
        if ( index == -1 )
            return null;
        
        return m_dimensions[ index ];
    }
    
    public final Image getImage()
    {
        return m_texture;
    }
    
    public final String getCharmap()
    {
        return m_charMap;
    }
    
    @Override
    protected int _load() throws IOException
    {
        InputStream is = getFileHandle().openFileForReading();
        
        try
        {
            SAXParserFactory factory = SAXParserFactory.newInstance();
            SAXParser parser = factory.newSAXParser();
            
            resetReader();
            parser.parse( is, new FontContentHandler() );
        } catch ( ParserConfigurationException e ) {
            e.printStackTrace();
            return -1;
        } catch ( SAXException e ) {
            e.printStackTrace();
            return -1;            
        }
        
        ResourceManager resMan = Framework.getSingleton().rman();
        FileSystem fsys = Framework.getSingleton().fsys();
        FileHandle textureHandle = fsys.createFileHandle(
            StringUtil.convertToAbsolutePath(
                getFileHandle().getParentDirectory(),
                m_readerTexturePath,
                '/', '/' ),
            m_readerTextureLocation
        );
        
        m_texture = (Image)resMan.loadResource( textureHandle, "image", new ImageCreationDetails() );
        m_texture.inc();
        return 0;
    }
    
    private void resetReader()
    {
        m_readingCharmap = m_readingDimensions = m_readingCharSpace =
            m_readingLineHeight = m_readingSpace = false;
        m_readerTexturePath = null;
        m_readerTextureLocation = FileHandle.LOC_INTERNAL;
    }
    
    @Override
    protected void _release()
    {
        m_texture.dec();
        m_texture = null;
        m_dimensions = null;
        m_charMap = null;
    }
    
    private class FontContentHandler extends DefaultHandler
    {
        @Override
        public void endElement( String uri, String localname, String qName )
        {
            if ( qName.equals( CHARMAP ) )
            {
                m_charMap = String.valueOf( m_charMapBuffer );
                m_charMapBuffer = null;
                /* $if SPUKMK3ME_DEBUG$ */
                Logger.log( "Charmap: " + m_charMap + '\n' );
                /* $endif$ */
                m_dimensions = new Rectangle[ m_charMap.length() ];
                m_readingCharmap = false;
            }
            else if ( qName.equals( DIMENSIONS ) )
                m_readingDimensions = false;
            else if ( qName.equals( LINEHEIGHT ) )
                m_readingLineHeight = false;
            else if ( qName.equals( CHARSPACE ) )
                m_readingCharSpace = false;
            else if ( qName.equals( SPACE ) )
                m_readingSpace = false;
        }
        
        @Override
        public void startElement( String uri, String localname, String qName, Attributes attributes )
        {
            if ( qName.equals( CHAR ) )
            {
                if ( m_readingDimensions )
                {
                    char id = attributes.getValue( 0 ).charAt( 0 );
                    int x = Integer.parseInt( attributes.getValue( 1 ) );
                    int y = Integer.parseInt( attributes.getValue( 2 ) );
                    int w = Integer.parseInt( attributes.getValue( 3 ) );
                    int h = Integer.parseInt( attributes.getValue( 4 ) );
                    
                    m_dimensions[ m_charMap.indexOf( id ) ] = new Rectangle( x, y, w, h );
                }
            }
            else if ( qName.equals( TEXTURE ) )
            {
                m_readerTexturePath = attributes.getValue( 0 );
                
                String loc = attributes.getValue( 1 ); 
                
                if ( loc.equals( INTERNAL ) )
                    m_readerTextureLocation = FileHandle.LOC_INTERNAL;
                else if ( loc.equals( EXTERNAL ) )
                    m_readerTextureLocation = FileHandle.LOC_EXTERNAL;
                else if ( loc.equals( REMOTE ) )
                    m_readerTextureLocation = FileHandle.LOC_REMOTE;
            }
            else if ( qName.equals( CHARMAP ) )
                m_readingCharmap = true;
            else if ( qName.equals( DIMENSIONS ) )
                m_readingDimensions = true;
            else if ( qName.equals( LINEHEIGHT ) )
                m_readingLineHeight = true;
            else if ( qName.equals( CHARSPACE ) )
                m_readingCharSpace = true;
            else if ( qName.equals( SPACE ) )
                m_readingSpace = true;
        }
        
        @Override
        public void characters( char[] data, int index, int length )
        {
            if ( m_readingCharmap )
            {
                if ( m_charMapBuffer == null )
                {
                    m_charMapBuffer = new char[ length ];
                    System.arraycopy( data, index, m_charMapBuffer, 0, length );
                }
                else
                {
                    char[] oldCharMapBuffer = m_charMapBuffer;
                    
                    m_charMapBuffer = new char[ oldCharMapBuffer.length + length ];
                    System.arraycopy( oldCharMapBuffer, 0, m_charMapBuffer, 0, oldCharMapBuffer.length );
                    System.arraycopy( data, index, m_charMapBuffer, oldCharMapBuffer.length, length );
                }
            }
            else if ( m_readingLineHeight )
            {
                m_lineHeight = Integer.parseInt( String.valueOf( data, index, length ) );
            }
            else if ( m_readingCharSpace )
            {
                m_charSpace = Integer.parseInt( String.valueOf( data, index, length ) );
            }
            else if ( m_readingSpace )
            {
                int space = Integer.parseInt( String.valueOf( data, index, length ) );
                
                m_spaceDimension = new Rectangle( 0, 0, space, m_lineHeight );
            }
        }
    }
    
    private static final String TEXTURE     = "texture";
    private static final String CHARMAP     = "charmap";
    private static final String DIMENSIONS  = "dimension";
    private static final String CHARSPACE   = "charspace";
    private static final String LINEHEIGHT  = "lineheight";
    private static final String SPACE       = "space";
    private static final String CHAR        = "char";
    private static final String INTERNAL    = "internal";
    private static final String EXTERNAL    = "external";
    private static final String REMOTE      = "remote";
    
    private Image   m_texture;
    private String  m_charMap;
    private char[]  m_charMapBuffer;
    private Rectangle[] m_dimensions;
    private Rectangle   m_spaceDimension; // Dimension for 'space'
    private int         m_lineHeight, m_charSpace;
    
    // Variables and constants for reader
    private String  m_readerTexturePath;
    private boolean m_readingCharmap, m_readingDimensions,
                    m_readingCharSpace, m_readingLineHeight, m_readingSpace;
    private byte    m_readerTextureLocation;
}